﻿using System;
using System.Collections.Generic;
using System.Exceptions;
using System.Linq;
using Infinity.Common;
using Infinity.Common.Logger;
using Infinity.Compilers;
using Infinity.Expressions.Constant;
using Infinity.Expressions.ExpressionFactory.CallExpression;
using Infinity.Expressions.ExpressionFactory.Constant;
using Infinity.Expressions.ExpressionFactory.Operator;
using Infinity.Expressions.ExpressionFactory.Symbol;
using Infinity.Expressions.ExpressionFactory.Undefined;
using Infinity.Expressions.Operator;
using Infinity.Expressions.Symbol;
using Infinity.Expressions.Undefined;
using Infinity.Expressions.Utilities;
using Infinity.Statements;
using Infinity.Statements.Program;
using System.Collections;
using Infinity.Statements.StatementFactory.Program;
using Infinity.Statements.Utilities;
using Infinity.Types;

namespace Infinity.Expressions.ExpressionFactory
{
	/// <summary>
	/// Factory class for creating expressions
	/// </summary>
	public class ExpressionFactory : IExpressionFactory
	{
		#region Private and protected properties

		private ICompiler m_Compiler { get; set; }

		private IScopeManager m_ScopeManager
		{
			get { return Ensure.NotNull(m_Compiler).ScopeManager; }
		}

		private ITypeManager m_TypeManager
		{
			get { return Ensure.NotNull(m_Compiler).TypeManager; }
		}

		private ICastManager m_CastManager
		{
			get { return Ensure.NotNull(m_Compiler).CastManager; }
		}

		#endregion

		#region Public properties

		public virtual ILogger Logger { get; set; }

		public virtual IStatementFactory StatementFactory { get; set; }

		#endregion

		#region Constructor

		public ExpressionFactory(ICompiler compiler, ILogger logger)
		{
			Ensure.NotNull("compiler", compiler);
			m_Compiler = compiler;
			Logger = logger;
		}

		public ExpressionFactory(ICompiler compiler)
			: this(compiler, DebugLogger.Instance)
		{
		}

		#endregion

		#region Factory methods

		public virtual IConstantExpression CreateNumberConstant(double value)
		{
			Logger.Info("Real number constant: " + value.ToString());
			return new RealNumberExpression()
				       {
					       Value = value,
					       ExpressionType = value.GetType()
				       };
		}

		public IConstantExpression CreateNumberConstant(long value)
		{
			Logger.Info("Integer number constant: " + value.ToString());
			return new IntegerNumberExpression()
				       {
					       Value = value,
					       ExpressionType = value.GetType()
				       };
		}

		public virtual IRangeExpression CreateRangeConstant(IExpression minimum, IExpression maximum, IExpression by)
		{
			Ensure.NotNull("minimum", minimum);

			Logger.Info("Range constant");

			return new RangeExpression
				       {
					       Minimum = minimum,
					       Maximum = maximum,
					       By = by,
					       ExpressionType = typeof(Range<>)
				       };
		}

		public virtual ISetExpression CreateSetConstant(params IExpression[] items)
		{
			Ensure.NotNull("items", items);

			Logger.Info("Set constant");
			return new SetExpression()
				       {
					       Items = items.Reverse(),
					       ExpressionType = typeof(ArrayList)
				       };
		}

		public virtual IBooleanExpression CreateBooleanConstant(bool value)
		{
			Logger.Info("Boolean constant: " + value);
			return new BooleanExpression()
				       {
					       Value = value,
					       ExpressionType = typeof(bool)
				       };
		}

		public virtual IStringExpression CreateStringConstant(string value)
		{
			Ensure.NotBlank(value);

			Logger.Info("String constant: " + value);

			var stringValue = value.Trim('\"');

			return new StringExpression()
				       {
					       Value = stringValue,
					       ExpressionType = typeof(string)
				       };
		}

		public ITupleExpression CreateTupleConstant(params IExpression[] expressions)
		{
			Ensure.NotNull("expressions", expressions);

			Logger.Info("Tuple constant");
			return new TupleExpression()
				       {
					       Arguments = expressions.ToList(),
					       ExpressionType = typeof(Tuple)
				       };
		}

		public IVariableStatement CreateLazySetIdentifier(string name, IExpression value)
		{
			Ensure.NotBlank(name);
			Ensure.NotNull("m_ScopeManager", m_ScopeManager);

			if (value is IIdentifierExpression)
			{
				Type result;
				if (!m_TypeManager.TryResolveType((value as IIdentifierExpression).Name, out result))
					Logger.Alert("Set must be a type!");

				value.ExpressionType = result;
			}

			var variable = new VariableStatement()
				               {
					               Name = name,
					               Type = value.ExpressionType,
					               Value = value
				               };
			m_ScopeManager.CreateScope().RegisterVariable(variable);

			return variable;
		}

		public virtual ILazySetExpression CreateLazySetConstant(
			IVariableStatement[] lazySetVariables, IExpression expression, IExpression condition)
		{
			Ensure.NotNull("lazySetVariables", lazySetVariables);
			Ensure.NotNull("condition", condition);

			Ensure.NotNull("Logger", Logger);

			Logger.Info("Lazy set constant");

			if (condition.ExpressionType != typeof(bool))
				Logger.Alert("Condition of lazy set must be boolean!");

			Ensure.NotNull(m_ScopeManager).RemoveScope();

			return new LazySetExpression
				       {
					       Variable = lazySetVariables,
					       Condition = condition,
					       Expression = expression,
					       ExpressionType = typeof(LazySet<>)
				       };
		}

		public virtual IExpression CreateReference(string value)
		{
			Ensure.NotBlank(value);

			if (StatementFactory == null) throw new UndefinedStatementFactory();

			//Search for operators
			var operatorList =
				StatementFactory.SymbolTable.Where(x => x is ICustomOperatorStatement && x.Name == value).ToArray();

			if (operatorList.Length > 0)
			{
				return new UndefinedOperatorExpression()
					       {
						       Value = value,
						       Operator = operatorList.First() as ICustomOperatorStatement
					       };
			}

			//Search for functions 
			var functionList = StatementFactory.SymbolTable.Where(x => x is IFunctionStatement && x.Name == value).ToArray();

			if (functionList.Length > 0)
			{
				return new UndefinedFunctionCallExpression()
					       {
						       Value = value,
						       Function = functionList.First() as IFunctionStatement
					       };
			}

			return CreateIdentifier(value);
		}

		public virtual IIdentifierExpression CreateIdentifier(string name)
		{
			Ensure.NotBlank(name);

			var variable = m_ScopeManager.GetVariable(name);

			Error.ThrowExceptionIf(variable == null, string.Format("There is no '{0}' variable!", name));

			return new IdentifierExpression()
				       {
					       Name = name,
					       ExpressionType = variable.Type,
					       Variable = variable
				       };
		}

		public IExpression CreateIndexer(params IExpression[] expressions)
		{
			Ensure.NotNull("expressions", expressions);

			throw new NotImplementedException();
		}

		public virtual IExpression CreateExpression(params IExpression[] expressions)
		{
			Ensure.NotNull("expressions", expressions);
			Ensure.NotNull("m_ScopeManager", m_ScopeManager);

			if (expressions.Length == 0) return null;
			if (expressions.Length == 1 &&
			    (expressions.First() is IConstantExpression || expressions.First() is IIdentifierExpression))
				return expressions.First();

			var expressionList = expressions.ToList();

			var functionList = expressionList.OfType<IUndefinedFunctionCallExpression>()
				.ToList();

			foreach (var currentFunction in functionList)
			{
				var index = expressionList.IndexOf(currentFunction);
				int parametersCount = currentFunction.Function.Parameters.Count;

				if (index + 1 < expressionList.Count)
				{
					var args = expressionList[index + 1];

					if (args is ITupleExpression)
					{
						if ((args as ITupleExpression).Arguments.Count != parametersCount) throw new Exception();

						expressionList.RemoveRange(index, 2);
						expressionList.Insert(index,
						                      new FunctionCallExpression()
							                      {
								                      Function = currentFunction.Function,
								                      Arguments = (args as ITupleExpression).Arguments,
								                      ExpressionType = currentFunction.Function.ReturnType
							                      });
					}
					else //függvényhívásnak kellene itt szerepelnie
					{
						//HIBA
					}
				}
				else
				{
					//HIBA
				}
			}

			var accessList = expressionList.OfType<IAccessOperatorExpression>()
				.ToList();

			foreach (var currentAccess in accessList)
			{
				var index = expressionList.IndexOf(currentAccess);

				if (index - 1 >= 0 && index + 1 < expressionList.Count)
				{
					var left = expressionList[index - 1];
					var right = expressionList[index + 1];

					if (left is IIdentifierExpression)
					{
						var identifier = left as IIdentifierExpression;
						var variable = m_ScopeManager.GetVariable(identifier.Name);
						left = variable.Value;
					}

					if (right is IIdentifierExpression)
					{
						var identifier = right as IIdentifierExpression;

						var leftExpressionType = left.ExpressionType;

						Error.ThrowExceptionIf(leftExpressionType == null, "Expression type of 'left' is missing!");

						var propertyList = leftExpressionType.GetProperties().Where(x => x.Name == identifier.Name).ToArray();

						if (propertyList.Any())
						{
							expressionList.RemoveRange(index - 1, 3);
							expressionList.Insert(index - 1,
							                      new PropertyCallExpression()
								                      {
									                      Name = identifier.Name,
									                      LeftExpression = left,
									                      PropertyInfo = propertyList.First(),
									                      ExpressionType = propertyList.First().PropertyType
								                      }
								);
						}
						else if (index + 2 < expressionList.Count)
						{
							var methodList = leftExpressionType.GetMethods().Where(x => !x.IsStatic && x.Name == identifier.Name).ToArray();

							var arguments = new List<IExpression>();

							if (expressionList[index + 2] is ITupleExpression)
							{
								arguments.AddRange(((ITupleExpression)expressionList[index + 2]).Arguments);
							}
							else
							{
								arguments.Add(expressionList[index + 2]);
							}

							foreach (var method in methodList)
							{
								var parameters = method.GetParameters();

								if (parameters.Length == arguments.Count)
								{
									bool match = true;

									for (var i = 0; i < parameters.Length && match; i++)
									{
										match = arguments[i].ExpressionType == parameters[i].ParameterType;
									}

									if (match)
									{
										expressionList.RemoveRange(index - 1, 4);
										expressionList.Insert(index - 1,
										                      new CallExpression.MethodCallExpression()
											                      {
												                      Arguments = arguments,
												                      LeftExpression = left,
												                      MethodInfo = method,
												                      Name = method.Name,
												                      ExpressionType = method.ReturnType
											                      }
											);

										break;
									}
									else //nem találtunk megfelelő metódust
									{
									}
								}
							}
						}
						else
						{
							//HIBA
						}
					}
					else
					{
						//HIBA
					}
				}
			}

			for (var i = 0; i < expressionList.Count; i++)
			{
				var item = expressionList[i] as ITupleExpression;
				if (item != null && item.Arguments.Count == 1)
				{
					expressionList[i] = item.Arguments.First();
				}
			}

			var operatorList =
				expressionList.Where(x => x is IUndefinedOperatorExpression && (x as IUndefinedOperatorExpression).Operator != null)
					.Select(x => x as IUndefinedOperatorExpression)
					.OrderByDescending(x => x.Operator.Precedence)
					.ToList();

			foreach (var currentOperator in operatorList)
			{
				var index = expressionList.IndexOf(currentOperator);
				int parametersCount = currentOperator.Operator.Parameters.Count;

				switch(currentOperator.Operator.OperatorType)
				{
					case OperatorType.Prefix:
						if (index + parametersCount < expressionList.Count)
						{
							var args = expressionList.GetRange(index + 1, parametersCount);

							for (var i = 0; i < currentOperator.Operator.Parameters.Count; i++)
							{
								if (args[i].ExpressionType != currentOperator.Operator.ReturnType)
								{
									throw new Exception();
								}
							}

							expressionList.RemoveRange(index, parametersCount + 1);

							expressionList.Insert(index,
							                      new PrefixOperatorExpression()
								                      {
									                      Arguments = args,
									                      ExpressionType = currentOperator.Operator.ReturnType,
									                      Operator = currentOperator.Operator
								                      });
						}
						else
						{
						}
						break;
					case OperatorType.Infix:
						if (parametersCount == 2)
						{
							if (index - 1 >= 0 && index + 1 < expressionList.Count)
							{
								var args = new[]
									           {
										           expressionList[index - 1], expressionList[index + 1]
									           };

								for (var i = 0; i < currentOperator.Operator.Parameters.Count; i++)
								{
									if (args[i].ExpressionType != currentOperator.Operator.ReturnType)
									{
										//throw new Exception();
									}
								}

								expressionList.RemoveRange(index - 1, parametersCount + 1);
								expressionList.Insert(index - 1,
								                      new InfixOperatorExpression()
									                      {
										                      Arguments = args,
										                      Operator = currentOperator.Operator,
										                      ExpressionType = currentOperator.Operator.ReturnType
									                      });
							}
							else
							{
							}
						}

						break;
					case OperatorType.Postfix:
						if (index - parametersCount >= 0)
						{
							var args = expressionList.GetRange(index - parametersCount, parametersCount);

							for (var i = 0; i < currentOperator.Operator.Parameters.Count; i++)
							{
								if (args[i].ExpressionType != currentOperator.Operator.ReturnType)
								{
									throw new Exception();
								}
							}

							expressionList.RemoveRange(index - parametersCount, parametersCount + 1);

							expressionList.Insert(index - parametersCount,
							                      new PostfixOperatorExpression()
								                      {
									                      Arguments = args,
									                      ExpressionType = currentOperator.Operator.ReturnType,
									                      Operator = currentOperator.Operator
								                      });
						}
						else
						{
						}
						break;
						//forall x in [1..13] : (expression)
					case OperatorType.Iterative:
						if (index + 5 < expressionList.Count)
						{
							var args = expressionList.GetRange(index + 1, 5);

							if (args[0] is IIdentifierExpression && args[1] is IInSymbolExpression && args[3] is IColonSymbolExpression)
							{
								if (args[2].ExpressionType == typeof(IEnumerable) ||
								    args[2].ExpressionType.GetInterface("IEnumerable", true) != null)
								{
									var expr = args[4];

									expressionList.RemoveRange(index, 6);

									expressionList.Insert(index, new IterativeOperatorExpression()
										                             {
											                             Arguments = new[]
												                                         {
													                                         args[0], args[2], args[4]
												                                         },
											                             Operator = currentOperator.Operator,
											                             ExpressionType = currentOperator.Operator.ReturnType
										                             });
								}
								else
								{
								}
							}
						}
						break;
					default:
						break;
				}
			}

			if (expressionList.Count != 1) throw new Exception();

			return expressionList.First();
		}

		public virtual IAccessOperatorExpression CreateAccessExpression()
		{
			return new AccessOperatorExpression();
		}

		public virtual IExpression CreateSymbol(string value)
		{
			Ensure.NotBlank(value);

			switch(value)
			{
				case ".":
					return new AccessOperatorExpression();

				case ",":
					return new CommaSymbolExpression();

				case ":":
					return new ColonSymbolExpression();

				case "in":
					return new InSymbolExpression();

				case "(":
					return new RegularParenthesesSymbolExpression()
						       {
							       ParenthesesType = ParenthesesType.Open
						       };

				case ")":
					return new RegularParenthesesSymbolExpression()
						       {
							       ParenthesesType = ParenthesesType.Close
						       };

				case "[":
					return new SquareParenthesesSymbolExpression()
						       {
							       ParenthesesType = ParenthesesType.Open
						       };

				case "]":
					return new SquareParenthesesSymbolExpression()
						       {
							       ParenthesesType = ParenthesesType.Close
						       };

				case "{":
					return new CurlyParenthesesSymbolExpression()
						       {
							       ParenthesesType = ParenthesesType.Open
						       };

				case "}":
					return new CurlyParenthesesSymbolExpression()
						       {
							       ParenthesesType = ParenthesesType.Close
						       };

				default:
					throw new Exception(string.Format("Unrecognized symbol: {0}", value));
			}
		}

		#endregion
	}
}